/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/graphics.hpp>

#include <baja/graphics/scenes/scenes_fwd.hpp>
#include <baja/graphics/scenes/scenes_interfaces.hpp>
#include <baja/graphics/scenes/details/scene_node_base.hpp>

namespace baja { namespace graphics { namespace scenes {

class prepared_scene : public iprepared_scene
{
protected:
    struct prepared_node
    {
        std::shared_ptr<iscene_node> node;
        float32 zDepth;
    };
    
public:
    prepared_scene(
        const std::shared_ptr<iscene_transformer>& sceneTransformer,
        prepared_scene_sort sort
        ) : _sceneTransformer(sceneTransformer), _sort(sort)
    {
    }

    static std::shared_ptr<prepared_scene> create(
        const std::shared_ptr<iscene_transformer>& sceneTransformer,
        prepared_scene_sort sort
        )
    {
        return std::shared_ptr<prepared_scene>(new prepared_scene(sceneTransformer, sort)); //  NOTE: I have no idea why if I do this I get a bogus compiler error std::make_shared<prepared_scene>(sceneTransformer, sort);
    }

    // gets the scene transformer for this prepared scene
    std::shared_ptr<iscene_transformer> getSceneTransformer()
    {
        return _sceneTransformer;
    }

    // gets the node count
    size_t getNodeCount()
    {
        return _nodes.size();
    }

    // gets the specified prepared node 
    std::shared_ptr<iscene_node> getNode(const size_t index)
    {
        BAJA_CHECK_ARG(index < _nodes.size());

        return _nodes[index].node;
    }

    // adds a node to the prepared scene
    void addNode(const std::shared_ptr<iscene_node>& node)
    {
        prepared_node newNode;

        // initialize the prepared_node struct
        newNode.node = node;

        // translate node into pixel space, in order to normalize Z from 0 to 1
        m::matrix44 localToWorld;
        (void)node->getLocalToWorld(&localToWorld);

        m::point3d localPoint;
        m::point3d worldPoint;
        m::point2d pixelPoint;

        localToWorld.apply(localPoint, &worldPoint);
        pixelPoint = _sceneTransformer->worldSpaceToPixel(worldPoint, &newNode.zDepth);

        this->addPreparedNode(newNode);
    }

    // renders the prepared scene
    void render(const std::shared_ptr<iengine>& graphics)
    {
        size_t count = _nodes.size();
        uint32 i;

        for (i = 0; i < count; i++)
        {
            _nodes[i].node->render(graphics);
        }
    }

private:

    void addPreparedNode(const prepared_node& node)
    {
        // Assume insertion at end
        size_t index = _nodes.size();
        
        switch (_sort)
        {
            case prepared_scene_sort::none:
                // Always append to end
                break;

            case prepared_scene_sort::frontToBack:
            {
                // Do insertion front-to-back
                for (uint32 i = 0; i < _nodes.size(); i++)
                {
                    if (_nodes[i].zDepth >= node.zDepth)
                    {
                        // Found a deeper node; insert here
                        index = i;
                        break;
                    }
                }
            }
            break;

            case prepared_scene_sort::backToFront:
            {
                // Do insertion back-to-front
                for (uint32 i = 0; i < _nodes.size(); i++)
                {
                    if (_nodes[i].zDepth <= node.zDepth)
                    {
                        // Found a shallower node; insert here
                        index = i;
                        break;
                    }
                }
            }
            break;

            case prepared_scene_sort::priority:
            {
                int32 priThis = 0;
                priThis = node.node->getRenderPriority();
                
                // Do insertion based on render priority
                for (uint32 i = 0; i < _nodes.size(); i++)
                {
                    int32 pri = 0;
                    pri = _nodes[i].node->getRenderPriority();
                    if (priThis < pri)
                    {
                        // Found a lower-priority node; insert here
                        index = i;
                        break;
                    }
                }
            }
            break;
        }

        if (index == _nodes.size())
        {
            _nodes.push_back(node);
        }
        else
        {
            _nodes.insert(_nodes.begin() + index, node);
        }
    }

private:
    std::vector<prepared_node> _nodes;
    std::shared_ptr<iscene_transformer> _sceneTransformer;
    prepared_scene_sort _sort;

};

}}} // namespace baja::graphics::scenes
